/**
 * This file is part of Lineage II Client Data Parser.
 * 
 * Lineage II Client Data Parser is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * Lineage II Client Data Parser is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Lineage II Client Data Parser.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.l2emuproject.entity;

import net.l2emuproject.entity.base.DefaultEntity;

/**
 * Here is some info I managed to dig out about Operate Type specified in the client.<BR>
 * The information seems consistent enough to claim that:
 * <LI>PDAM = 0</LI>
 * <LI>ITEM CREATION + TRANSFORMED SKILLS = 1</LI>
 * <LI>BUFF = 2</LI>
 * <LI>DEBUFF = 3</LI>
 * <LI>SIEGE = 4</LI>
 * <LI>ITEM = 5 (active + fishing)</LI>
 * <LI>TOGGLE = 6</LI>
 * <LI>TRANSFORM = 7</LI>
 * <LI>PASSIVE_PHYSIC = 11</LI>
 * <LI>PASSIVE_SPECIAL [starter, npc, test] = 12</LI>
 * <LI>PASSIVE_RACIAL = 13</LI>
 * <LI>PASSIVE_MAGIC = 14</LI>
 * <LI>PASSIVE_CLAN = 15</LI>
 * <LI>PASSIVE_ITEM = 16</LI>
 * 
 * @author Savormix
 */
public class SkillEntry extends DefaultEntity
{
	/** Default reuse delay added to non-passive skills */
	public static final int			DEFAULT_REUSE		= 1000;

	private final int				id;
	private final int				level;
	private final String			nameS;
	private final String			descS;
	private final String			nameE;
	private final String			descE;
	private int						operate				= 12;											//passive [special] skill
	private int						unk_0				= 0;
	private int						mana				= 0;
	private int						range				= -1;
	private int						style				= 1;
	private float					time				= 0F;
	private boolean					magic				= false;
	private String					animation			= "";
	private String					desc				= "";
	private String					icon1				= "";
	private String					icon2				= "";
	private boolean					enchanted			= false;
	private int						enchSID				= 0;
	private int						health				= 0;
	private int						unk_1				= 0;
	private int						unk_2				= 9;
	private int						unk_3				= -1;
	// Deterministic
	private static final char		PERIOD				= '.';
	private static final String		NULL				= "";
	private static final String		RAID				= "Raid Boss";
	/** The first mark is used in Chronicle 7, the second in Chronicle 8+ */
	private static final String[]	PERCENT				=
														{ "%", " percent" };
	private static final String		P_ATK_POWER			= "is increased";
	private static final String		POWER_CHANGES		= "changes";
	private static final String		POWER_INCREASES		= "increases";
	private static final String		WITH_POWER_1		= " with ";
	private static final String		WITH_POWER_2		= " using ";
	private static final String		WITH_POWER_3		= " by ";
	private static final String		POWER_1				= ". Power ";
	private static final String		POWER_2				= " Power. ";
	private static final String		POWER_3				= " Power a";
	private static final String		POWER_4				= " Power t";
	private static final String[]	WEAPON_TYPE			=
														{ " Requires a ", " when using a " };
	private static final String		CHARGE_MAX			= "charged up to ";
	private static final String[]	CHARGE_LEVEL		=
														{ "level ", "Level " };
	private static final String		OVERHIT				= "Over-hit";
	private static final String[]	IGNORE_SHIELD		=
														{ "ignoring", "Shield defense is ignored" };
	private static final String		EFFECT_POWER		= ". Effect ";
	private static final String		EFFECT_APPLIES		= "applies";
	private static final String		NEGATE_POWER		= "less than ";
	private static final String		CRITICAL			= "ritical hit is poss";
	private static final String		DANCE				= "Dance of";
	private static final String		SONG				= "Song of";
	private static final int		DANCE_ID			= 1;
	private static final int		SONG_ID				= 2;
	private static final String[]	TRG					=
														{
			"TARGET_SELF",
			"TARGET_ONE",
			"TARGET_AURA",
			"TARGET_AREA",
			"TARGET_PARTY",
			"TARGET_PARTY_MEMBER",
			"TARGET_CLAN",
			"TARGET_PET"								};
	private static final String[]	TRG_PARTY			=
														{ "party member", "party members", "party's" };
	private static final int		RADIUS_PARTY		= 1000;
	private static final String		TRG_PET				= "servitor's";
	private static final String[]	TRG_AURA			=
														{ "rounding enem", "nearby enem" };
	private static final int		RADIUS_AURA			= 200;
	private static final String		SEAL				= "Seal of";
	private static final int		RADIUS_SEAL			= 300;
	private static final String		TRG_CLAN			= "clan memb";
	private static final int		RADIUS_CLAN			= 900;
	private static final String		OPERATE_ACTIVE		= "OP_ACTIVE";
	private static final String		OPERATE_TOGGLE		= "OP_TOGGLE";
	private static final String		OPERATE_PASSIVE		= "OP_PASSIVE";
	/** Indicates a skill that is not usable [yet] */
	public static final String		SKILL_NOTDONE		= "NOTDONE";
	private static final String		SKILL_BUFF			= "BUFF";
	private static final String		SKILL_DEBUFF		= "DEBUFF";
	private static final String		SKILL_DMG_MAGIC		= "MDAM";
	/** Indicates a skill that is based on physical power */
	public static final String		SKILL_DMG_PHYSIC	= "PDAM";
	private static final String		SKILL_DRAIN			= "DRAIN";
	private static final String		SKILL_HQ			= "SIEGEFLAG";
	private static final String		SKILL_HOT			= "HOT";
	private static final String		SKILL_MAKE_ITEM		= "CREATE_ITEM";
	private static final String		SKILL_PROVOKE		= "AGGDAMAGE";
	private static final String		SKILL_RAID			= "PASSIVE";
	private static final String		SKILL_ROOT			= "ROOT";
	private static final String		SKILL_SURRENDER		= "WEAKNESS";
	private static final String		SKILL_TOGGLE		= "CONT";
	private static final String		DRAIN				= "Absorbs HP";
	private static final String[]	MAKE				=
														{ "uiver", "reate" };
	private static final String		PROVOKE				= "rovoke";
	private static final String		ROOT				= "hold";
	private static final String		SURRENDER			= "urrender";
	private static final String[]	SKILL_EFFECT		=
														{ "emporarily", "by your side" };
	private static final int		LENGTH_BUFF			= 1200;
	private static final int		LENGTH_DEBUFF		= 15;
	private static final int		LENGHT_TOGGLE		= 3;
	private boolean					raidDesc			= false;
	private int						power				= 0;
	private boolean					overhit				= false;
	private boolean					ignoreShield		= false;
	private int						danceType			= 0;
	private int						weaponToCast		= 0;
	private int						charges				= 0;
	private String					skillType			= NULL;
	private String					targetType			= NULL;
	private int						radius				= 0;
	private int						critRate			= 0;
	private int						elementType			= 0;
	private int						elementPower		= 0;
	private float					absorb				= 0;
	private int						dependency			= 0;
	private boolean					staticReuse			= false;
	private boolean					debuff				= false;
	private int						effectCnt			= 0;
	private String					effectType			= NULL;
	private String					effectState			= NULL;
	private int						effectLength		= 0;
	private float					effectDmg			= 0F;
	private int						effectLevel			= 0;
	private int						negateLevel			= 0;
	private String					negateTypes			= NULL;
	private String					effectStackType		= NULL;

	// Experimental

	/**
	 * Creates a skill entry containing base data
	 * @param id Skill ID
	 * @param level Skill level
	 * @param nameS Skill name
	 * @param descS Skill description
	 * @param nameE Skill enchant name
	 * @param descE Skill enchant description
	 */
	public SkillEntry(int id, int level, String nameS, String descS, String nameE, String descE)
	{
		this.id = id;
		this.level = level;
		this.nameS = nameS;
		this.descS = descS;
		this.nameE = nameE;
		this.descE = descE;
	}

	/** @return the skill ID */
	public final int getId()
	{
		return id;
	}

	/** @return the skill level */
	public final int getLevel()
	{
		return level;
	}

	/** @return the skill name */
	public final String getNameS()
	{
		return nameS;
	}

	/** @return the skill description */
	public final String getDescS()
	{
		return descS;
	}

	/** @return the skill enchant name */
	public final String getNameE()
	{
		return nameE;
	}

	/** @return the skill enchant description */
	public final String getDescE()
	{
		return descE;
	}

	/**
	 * @return the operate type
	 */
	public final int getOperateType()
	{
		return operate;
	}

	/**
	 * @param operate the operate type to set
	 */
	public final void setOperateType(int operate)
	{
		this.operate = operate;
	}

	/**
	 * @return the declarable operate type
	 * @see SkillEntry
	 */
	public final String getOpType()
	{
		switch (getOperateType())
		{
			case 0:
			case 1:
			case 2:
			case 3:
			case 4:
			case 5:
			case 7:
				return OPERATE_ACTIVE;
			case 6:
				return OPERATE_TOGGLE;
			default:
				return OPERATE_PASSIVE;
		}
	}

	/**
	 * @return the unk_0
	 */
	public final int getUnk_0()
	{
		return unk_0;
	}

	/**
	 * @param unk_0 the unk_0 to set
	 */
	public final void setUnk_0(int unk_0)
	{
		this.unk_0 = unk_0;
	}

	/**
	 * @return the consumed mana point count
	 */
	public final int getMana()
	{
		return mana;
	}

	/**
	 * @param mana the consumed mana point count to set
	 */
	public final void setMana(int mana)
	{
		this.mana = mana;
	}

	/**
	 * @return the maximum range to start casting
	 */
	public final int getRange()
	{
		return range;
	}

	/** @return {@link #getRange()}, unless it's -1, then returns 0 */
	public final int getCastRange()
	{
		return getRange() == -1 ? 0 : getRange();
	}

	/**
	 * @return the maximum range to keep casting
	 */
	public final int getEffectiveRange()
	{
		if (getRange() >= 100)
			return getRange() + 500;
		else if (getRange() >= 10)
			return getRange() * 10;
		else
			return -1;
	}

	/** @return {@link #getEffectiveRange()}, unless it's -1, then returns 0 */
	public final int getEffectRange()
	{
		return getEffectiveRange() == -1 ? 0 : getEffectiveRange();
	}

	/**
	 * @param range the maximum range to start casting to set
	 */
	public final void setRange(int range)
	{
		this.range = range;
	}

	/**
	 * @return the cast style
	 */
	public final int getStyle()
	{
		return style;
	}

	/**
	 * @param style the cast style to set
	 */
	public final void setStyle(int style)
	{
		this.style = style;
	}

	/**
	 * @return the time required to cast the skill in seconds
	 */
	public float getTime()
	{
		return time;
	}

	/**
	 * @return the time required to cast the skill in milliseconds
	 */
	public int getHitTime()
	{
		return (int) time * 1000;
	}

	/**
	 * @return the minimum cooldown (you are not able to cast) time in milliseconds
	 */
	public int getCoolTime()
	{
		return 0;
		//return (getHitTime() / 3 * 2);
	}

	/**
	 * @param time the time required to cast the skill in seconds to set
	 */
	public void setTime(float time)
	{
		this.time = time;
	}

	/**
	 * @return is this skill - magic
	 */
	public final boolean isMagic()
	{
		return magic;
	}

	/**
	 * @param magic the magic attribute to set
	 */
	public final void setMagic(boolean magic)
	{
		this.magic = magic;
	}

	/**
	 * @return the animation char
	 */
	public final String getAnimationChar()
	{
		return animation;
	}

	/**
	 * @param animation the animation char to set
	 */
	public final void setAnimationChar(String animation)
	{
		this.animation = animation;
	}

	/**
	 * @return the desc
	 */
	public final String getDesc()
	{
		return desc;
	}

	/**
	 * @param desc the desc to set
	 */
	public final void setDesc(String desc)
	{
		this.desc = desc;
	}

	/**
	 * @return the icon1
	 */
	public final String getIcon1()
	{
		return icon1;
	}

	/**
	 * @param icon1 the icon1 to set
	 */
	public final void setIcon1(String icon1)
	{
		this.icon1 = icon1;
	}

	/**
	 * @return the icon2
	 */
	public final String getIcon2()
	{
		return icon2;
	}

	/**
	 * @param icon2 the icon2 to set
	 */
	public final void setIcon2(String icon2)
	{
		this.icon2 = icon2;
	}

	/**
	 * @return is this an enchanted skill
	 */
	public final boolean isEnchanted()
	{
		return enchanted || getLevel() >= 100;
	}

	/**
	 * @param enchanted the enchanted attribute to set
	 */
	public final void setEnchanted(boolean enchanted)
	{
		this.enchanted = enchanted;
	}

	/**
	 * @return the base skill ID
	 */
	public final int getEnchSID()
	{
		return enchSID;
	}

	/**
	 * @param enchSID the base skill ID to set
	 */
	public final void setEnchSID(int enchSID)
	{
		this.enchSID = enchSID;
	}

	/**
	 * @return the consumed health point count
	 */
	public final int getHealth()
	{
		return health;
	}

	/**
	 * @param health the consumed health point count to set
	 */
	public final void setHealth(int health)
	{
		this.health = health;
	}

	/**
	 * @return the unk_1
	 */
	public final int getUnk_1()
	{
		return unk_1;
	}

	/**
	 * @param unk_1 the unk_1 to set
	 */
	public final void setUnk_1(int unk_1)
	{
		this.unk_1 = unk_1;
	}

	/**
	 * @return the unk_2
	 */
	public final int getUnk_2()
	{
		return unk_2;
	}

	/**
	 * @param unk_2 the unk_2 to set
	 */
	public final void setUnk_2(int unk_2)
	{
		this.unk_2 = unk_2;
	}

	/**
	 * @return the unk_3
	 */
	public final int getUnk_3()
	{
		return unk_3;
	}

	/**
	 * @param unk_3 the unk_3 to set
	 */
	public final void setUnk_3(int unk_3)
	{
		this.unk_3 = unk_3;
	}

	/**
	 * @return the power
	 */
	public final int getPower()
	{
		return power;
	}

	/**
	 * @param power the power to set
	 */
	public final void setPower(int power)
	{
		this.power = power;
	}

	/**
	 * @return the animation
	 */
	public final String getAnimation()
	{
		return animation;
	}

	/**
	 * @param animation the animation to set
	 */
	public final void setAnimation(String animation)
	{
		this.animation = animation;
	}

	/**
	 * @return the overhit
	 */
	public final boolean isOverhit()
	{
		return overhit;
	}

	/**
	 * @param overhit the overhit to set
	 */
	public final void setOverhit(boolean overhit)
	{
		this.overhit = overhit;
	}

	/**
	 * @return the ignoreShield
	 */
	public final boolean ignoresShield()
	{
		return ignoreShield;
	}

	/**
	 * @param ignoreShield the ignoreShield to set
	 */
	public final void setIgnoresShield(boolean ignoreShield)
	{
		this.ignoreShield = ignoreShield;
	}

	/**
	 * @return is the skill a dance
	 */
	public final boolean isDance()
	{
		return (danceType == DANCE_ID);
	}

	/**
	 * @return is the skill a song
	 */
	public final boolean isSong()
	{
		return (danceType == SONG_ID);
	}

	/**
	 * @param danceType the danceType to set
	 */
	public final void setDanceType(int danceType)
	{
		this.danceType = danceType;
	}

	/**
	 * @return the weaponsAllowed mask
	 */
	public final int getWeaponToCast()
	{
		return weaponToCast;
	}

	/**
	 * Adds a weapon to the weaponsAllowed mask if it wasn't yet added.
	 * @param weaponMask weapon type to add
	 */
	public final void addWeaponToCast(int weaponMask)
	{
		if ((getWeaponToCast() & weaponMask) != weaponMask)
			weaponToCast += weaponMask;
	}

	/**
	 * @param weaponToCast the weaponToCast to set
	 */
	public final void setWeaponToCast(int weaponToCast)
	{
		this.weaponToCast = weaponToCast;
	}

	/**
	 * @return true, if the skill type must be RAIDBOSS_DESCRIPTION
	 */
	public final boolean isRaidDesc()
	{
		return raidDesc;
	}

	/**
	 * @param raidDesc the raidDesc to set
	 */
	public final void setRaidDesc(boolean raidDesc)
	{
		this.raidDesc = raidDesc;
	}

	/**
	 * @return the maximum charges gained using this skill
	 */
	public final int getCharges()
	{
		return charges;
	}

	/**
	 * @param charges the maximum charge count to set
	 */
	public final void setCharges(int charges)
	{
		this.charges = charges;
	}

	/**
	 * @return the charges gained using this skill
	 */
	public final int getChargesGiven()
	{
		if (getCharges() > 0)
			return 1;
		else
			return 0;
	}

	/**
	 * @return the effectLevel
	 */
	public final int getEffectLevel()
	{
		return effectLevel;
	}

	/**
	 * @param effectLevel the effectLevel to set
	 */
	public final void setEffectLevel(int effectLevel)
	{
		this.effectLevel = effectLevel;
	}

	/**
	 * @return the negateLevel
	 */
	public final int getNegateLevel()
	{
		return negateLevel;
	}

	/**
	 * @param negateLevel the negateLevel to set
	 */
	public final void setNegateLevel(int negateLevel)
	{
		this.negateLevel = negateLevel;
	}

	/**
	 * @return the negateTypes
	 */
	public final String getNegateTypes()
	{
		return negateTypes;
	}

	/**
	 * @param negateTypes the negateTypes to set
	 */
	public final void setNegateTypes(String negateTypes)
	{
		this.negateTypes = negateTypes;
	}

	/**
	 * @return the skill type
	 */
	public final String getSkillType()
	{
		return skillType;
	}

	/**
	 * @param skillType the skill type to set
	 */
	public final void setSkillType(String skillType)
	{
		this.skillType = skillType;
	}

	/**
	 * @return the target type
	 */
	public final String getTargetType()
	{
		return targetType;
	}

	/**
	 * @param targetType the target type to set
	 */
	public final void setTargetType(String targetType)
	{
		this.targetType = targetType;
	}

	/**
	 * @return the skill's critical rate
	 */
	public final int getCritRate()
	{
		return critRate;
	}

	/**
	 * @param critRate the skill's critical rate to set
	 */
	public final void setCritRate(int critRate)
	{
		this.critRate = critRate;
	}

	/**
	 * @return the element type
	 */
	public final int getElementType()
	{
		return elementType;
	}

	/**
	 * @param elementType the element type to set
	 */
	public final void setElementType(int elementType)
	{
		this.elementType = elementType;
	}

	/**
	 * @return the elemental power
	 */
	public final int getElementPower()
	{
		return elementPower;
	}

	/**
	 * @param elementPower the elemental power to set
	 */
	public final void setElementPower(int elementPower)
	{
		this.elementPower = elementPower;
	}

	/**
	 * @return the absorb part
	 */
	public final float getAbsorb()
	{
		return absorb;
	}

	/**
	 * @param absorb the absorb part to set
	 */
	public final void setAbsorb(float absorb)
	{
		this.absorb = absorb;
	}

	/**
	 * @return the level dependency type
	 */
	public final int getDependency()
	{
		return dependency;
	}

	/**
	 * @param dependency the level dependency type to set
	 */
	public final void setDependency(int dependency)
	{
		this.dependency = dependency;
	}

	/**
	 * @return the staticReuse
	 */
	public final boolean isStaticReuse()
	{
		return staticReuse;
	}

	/**
	 * @param staticReuse the staticReuse to set
	 */
	public final void setStaticReuse(boolean staticReuse)
	{
		this.staticReuse = staticReuse;
	}

	/**
	 * @return the skill's effect radius
	 */
	public final int getRadius()
	{
		return radius;
	}

	/**
	 * @param radius the skill's effect radius to set
	 */
	public final void setRadius(int radius)
	{
		this.radius = radius;
	}

	/**
	 * @return skill's effect is a debuff
	 */
	public final boolean isDebuff()
	{
		return debuff;
	}

	/**
	 * @param debuff mark this skill as a debuff
	 */
	public final void setDebuff(boolean debuff)
	{
		this.debuff = debuff;
	}

	/**
	 * @return the effect count
	 */
	public final int getEffectCount()
	{
		return effectCnt;
	}

	/**
	 * @param effectCnt the effect count to set
	 */
	public final void setEffectCount(int effectCnt)
	{
		this.effectCnt = effectCnt;
	}

	/**
	 * @return the effect type
	 */
	public final String getEffectType()
	{
		return effectType;
	}

	/**
	 * @param effectType the effect type to set
	 */
	public final void setEffectType(String effectType)
	{
		this.effectType = effectType;
	}

	/**
	 * @return the effect abnormal state
	 */
	public final String getEffectState()
	{
		return effectState;
	}

	/**
	 * @param effectState the effect abnormal state to set
	 */
	public final void setEffectState(String effectState)
	{
		this.effectState = effectState;
	}

	/**
	 * @return the effect length in seconds
	 */
	public final int getEffectLength()
	{
		return effectLength;
	}

	/**
	 * @param effectLength the effect length to set
	 */
	public final void setEffectLength(int effectLength)
	{
		this.effectLength = effectLength;
	}

	/**
	 * @return the effectDmg
	 */
	public final float getEffectDmg()
	{
		return effectDmg;
	}

	/**
	 * @param effectDmg the effectDmg to set
	 */
	public final void setEffectDmg(float effectDmg)
	{
		this.effectDmg = effectDmg;
	}

	/**
	 * @return the effectStackType
	 */
	public final String getEffectStackType()
	{
		return effectStackType;
	}

	/**
	 * @param effectStackType the effectStackType to set
	 */
	public final void setEffectStackType(String effectStackType)
	{
		this.effectStackType = effectStackType;
	}

	/** @return whether the skill is passive */
	public final boolean isPassive()
	{
		return getOpType().equals(OPERATE_PASSIVE);
	}

	/** @return whether the skill is toggle */
	public final boolean isToggle()
	{
		return getOperateType() == 6;
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.entity.IAnalysis#deterministicAnalysis()
	 */
	@Override
	public void deterministicAnalysis()
	{
		String s;
		// The function of getting a number from a specific sentence is as follows:
		// Get the beggining of the sentence
		int index = getDescS().lastIndexOf(POWER_1);
		int len = POWER_1.length();
		if (index != -1)
		{
			// The sentence was found, let's trash the known part
			s = getDescS().substring(index + len);
			// Period marks the end of THIS sentence
			int pIndex = s.indexOf(PERIOD);
			try
			{
				if (pIndex != -1)
					// Found a period, discard it and everything after
					s = s.substring(0, pIndex);
				if (!s.startsWith(P_ATK_POWER) && !s.startsWith(POWER_CHANGES) && !s.startsWith(POWER_INCREASES))
					// This isn't a buff description, so get the integer
					power = Integer.parseInt(s.replace(PERCENT[0], NULL).replace(PERCENT[1], NULL).trim());
				else
				{
					index = getDescS().indexOf(POWER_2);
					if (index == -1)
						index = getDescS().indexOf(POWER_3);
					if (index == -1)
						index = getDescS().indexOf(POWER_4);
					int with = getDescS().indexOf(WITH_POWER_1, index - 10);
					if (with == -1 || with > index)
					{
						with = getDescS().indexOf(WITH_POWER_2, index - 11);
						if (with == -1 || with > index)
							with = getDescS().indexOf(WITH_POWER_3, index - 8) + WITH_POWER_3.length();
						else
							with += WITH_POWER_2.length();
					}
					else
						with += WITH_POWER_1.length();
					// if another Power is found
					if (index != -1)
						power = Integer.parseInt(getDescS().substring(with, index));
				}
			}
			catch (NumberFormatException nfe)
			{
				log.warn("Omg, power parsing problems in " + this, nfe);
				log.debug(getDescS());
			}
			catch (StringIndexOutOfBoundsException e)
			{
				log.warn("The algorithm is no longer suitable!", e);
				log.debug(getDescS());
			}
		}
		index = getDescS().lastIndexOf(WEAPON_TYPE[0]);
		len = WEAPON_TYPE[0].length();
		if (index == -1)
		{
			index = getDescS().lastIndexOf(WEAPON_TYPE[1]);
			len = WEAPON_TYPE[1].length();
		}
		if (index != -1)
		{
			s = getDescS().substring(index + len);
			index = s.indexOf(PERIOD);
			if (index != -1)
			{
				s = s.substring(0, index);
				for (WeaponType wt : WeaponType.values())
				{
					for (String id : wt.getIdent())
					{
						if (s.contains(id))
						{
							addWeaponToCast(wt.getMask());
							s = s.replace(id, NULL);
						}
					}
				}
			}
		}
		for (WeaponType wt : WeaponType.values())
			for (String id : wt.getAdvIdent())
				//if (weaponToCast == 0 && getDescS().contains(id))
				if (getDescS().contains(id))
					addWeaponToCast(wt.getMask());
		index = getDescS().lastIndexOf(CHARGE_MAX);
		if (index != -1)
		{
			int t = index + CHARGE_MAX.length();
			index = getDescS().lastIndexOf(CHARGE_LEVEL[0], t);
			len = CHARGE_LEVEL[0].length();
			if (index == -1)
			{
				index = getDescS().lastIndexOf(CHARGE_LEVEL[1], t);
				len = CHARGE_LEVEL[1].length();
			}
			if (index != -1)
			{
				s = getDescS().substring(index + len);
				index = s.indexOf(PERIOD);
				try
				{
					s = s.substring(0, index);
					charges = Integer.parseInt(s);
				}
				catch (NumberFormatException nfe)
				{
					log.warn("Omg, charge count parsing problems in " + this, nfe);
					log.debug(getDescS());
				}
			}
		}
		// basic stack order - by [each] skill's level
		effectLevel = getLevel();
		// try to get NC's order (Effect X.)
		index = getDescS().lastIndexOf(EFFECT_POWER);
		len = EFFECT_POWER.length();
		if (index != -1)
		{
			s = getDescS().substring(index + len);
			index = s.indexOf(PERIOD);
			try
			{
				if (index != -1)
					s = s.substring(0, index);
				s = s.trim();
				if (s.contains(NEGATE_POWER))
					negateLevel = Integer.parseInt(s.replace(NEGATE_POWER, NULL).trim());
				else if (!s.startsWith(EFFECT_APPLIES))
					effectLevel = Integer.parseInt(s);
			}
			catch (NumberFormatException nfe)
			{
				log.warn("Omg, effect power parsing problems in " + this, nfe);
				log.debug(getDescS());
			}
			catch (StringIndexOutOfBoundsException e)
			{
				log.warn("The algorithm is no longer suitable!", e);
			}
		}
		// Raid description = icon + text, nothing else
		raidDesc = getNameS().startsWith(RAID);
		// Skill might give additional XP if used to kill a monster
		overhit = getDescS().contains(OVERHIT);
		// Skill might ignore shield defense
		ignoreShield = (getDescS().contains(IGNORE_SHIELD[0]) || getDescS().contains(IGNORE_SHIELD[1]));
		// Skill might be a dance
		if (getNameS().startsWith(DANCE))
			danceType = DANCE_ID;
		// Skill might be a song
		else if (getNameS().startsWith(SONG))
			danceType = SONG_ID;
		// initial target type
		if (getRange() < 0)
			targetType = TRG[0]; // self
		else
			targetType = TRG[1]; // one
		// target aura (most AoE skills)
		if (getDescS().contains(TRG_AURA[0]) || getDescS().contains(TRG_AURA[1]))
		{
			targetType = TRG[2]; // aura
			if (getNameS().startsWith(SEAL))
				radius = RADIUS_SEAL;
			else
				radius = RADIUS_AURA;
		}
		// TARGET_AREA isn't easily derivable
		// target party [member] (mass buffs)
		if (getDescS().contains(TRG_PARTY[0]))
		{
			if (getDescS().contains(TRG_PARTY[1]) || getDescS().contains(TRG_PARTY[2]))
			{
				targetType = TRG[4]; // party
				radius = RADIUS_PARTY;
			}
			else
				targetType = TRG[5]; // party member
		}
		// target clan (overlord buffs)
		if (getDescS().contains(TRG_CLAN))
		{
			targetType = TRG[6];
			radius = RADIUS_CLAN;
		}
		// target pet (skills used on servitors)
		if (getDescS().contains(TRG_PET))
			targetType = TRG[7];
		// Skill might score a critical hit
		if (getDescS().contains(CRITICAL))
			critRate = 15;
		switch (getOperateType())
		{
			case 0:
			case 1:
				if (getNameS().contains(MAKE[0]) || getNameS().contains(MAKE[1]))
					skillType = SKILL_MAKE_ITEM;
				if (isMagic())
					skillType = SKILL_DMG_MAGIC;
				else
					skillType = SKILL_DMG_PHYSIC;
				break;
			case 2:
			case 7:
			case 11:
			case 12:
			case 13:
			case 14:
			case 15:
			case 16:
				skillType = SKILL_BUFF;
				break;
			case 3:
				skillType = SKILL_DEBUFF;
				debuff = true;
				break;
			case 4: //not only flag
				skillType = SKILL_HQ;
				break;
			case 5: //not only
				skillType = SKILL_HOT;
				break;
			case 6: // continuous buff
				skillType = SKILL_TOGGLE;
				break;
			default:
				skillType = SKILL_NOTDONE;
				log.debug("Unknown operate type " + getOperateType() + " for " + this);
				break;
		}
		if (getDescS().contains(SKILL_EFFECT[0]) || getDescS().contains(SKILL_EFFECT[1]))
		{
			if (isDebuff())
			{
				skillType = SKILL_DEBUFF;
				effectCnt = 1;
				effectType = "Debuff";
				effectDmg = 0;
				effectLength = LENGTH_DEBUFF;
			}
			else
			{
				skillType = SKILL_BUFF;
				effectCnt = 1;
				effectType = "Buff";
				effectDmg = 0;
				effectLength = LENGTH_BUFF;
			}
		}
		if (isRaidDesc())
			skillType = SKILL_RAID;
		// Skill might absorb HP
		if (getDescS().contains(DRAIN))
		{
			skillType = SKILL_DRAIN;
			absorb = 0.4F;
			if (!isMagic())
			{
				absorb *= 2;
				staticReuse = true;
			}
			else if (isEnchanted())
				for (int i = 0; i < getLevel() % 100; i++)
					absorb += 0.01F;
			elementType = Element.DARK;
			elementPower = Element.POWER_DEFAULT;
		}
		// Skill might immobilize
		if (getDescS().contains(ROOT))
		{
			skillType = SKILL_ROOT;
			elementType = Element.EARTH;
			elementPower = Element.POWER_DEFAULT;
		}
		// Skill might decrease elemental defense
		if (getDescS().contains(SURRENDER))
		{
			skillType = SKILL_SURRENDER;
			power = 80;
			dependency = 2;
			debuff = true;
		}
		// Skill might draw monsters
		if (getDescS().contains(PROVOKE) || getNameS().contains(PROVOKE))
		{
			skillType = SKILL_PROVOKE;
			staticReuse = true;
		}
		if (getOperateType() == 6)
		{
			effectCnt = Integer.MAX_VALUE;
			effectLength = LENGHT_TOGGLE;
			switch (getId())
			{
				case 221:
					effectType = "SilentMove";
					effectDmg = 15;
					effectState = "stealth";
					break;
				case 222:
					effectType = "DamOverTime";
					effectLength = 1;
					effectDmg = 13;
					break;
				case 226:
					effectType = "Relax";
					effectLength = 2;
					effectDmg = 2;
					break;
				case 256:
				case 1262:
					effectType = "MpConsumePerLevel";
					effectDmg = 0.2F;
					break;
				case 288:
					effectType = "MpConsumePerLevel";
					effectDmg = 1;
					break;
				case 296:
					effectType = "ChameleonRest";
					effectState = "stealth";
					effectDmg = 6;
					break;
				default:
					effectType = "ManaDamOverTime";
					effectDmg = 15;
					break;
			}
		}
	}

	/* (non-Javadoc)
	 * @see com.l2cdp.entity.IAnalysis#experimentalAnalysis()
	 */
	@Override
	public void experimentalAnalysis()
	{ /* Your ideas here */
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString()
	{
		return getNameS() + " Lv" + getLevel();
	}

	@SuppressWarnings("unused")
	private static class Element
	{
		private static final int	FIRE			= 0;
		private static final int	WATER			= 1;
		private static final int	WIND			= 2;
		private static final int	EARTH			= 3;
		private static final int	HOLY			= 4;
		private static final int	DARK			= 5;
		private static final int	POWER_DEFAULT	= 20;
	}

	private enum WeaponType
	{
		SHIELD(1, "shield", null),
		DUAL_SWORD(9, "dual-sword", null),
		TWO_H_SWORD(11, "two-handed sword", null),
		ANCIENT_SWORD(15, "ancient sword", null),
		SWORD(2, "sword", null),
		TWO_H_BLUNT(14, new String[]
		{ "two handed blunt", "two-handed blunt" }, null),
		BLUNT(3, "blunt", null),
		DAGGER(4, "dagger", null),
		BOW(5, "bow", "from a bow"),
		POLE(6, "polearm", null),
		BOOK(7),
		HAND_FIST(8),
		DUAL_FIST(10, "hand-to-hand", null),
		PET_WEAPON(12),
		FISHING_ROD(13, null, "fishing"),
		CROSSBOW(16, "crossbow", null),
		RAPIER(17, "rapier", null);

		private final int		mask;
		private final String[]	ident;
		private final String[]	advIdent;

		private WeaponType(int maskID, String[] ident, String[] advIdent)
		{
			this.mask = 1 << maskID;
			if (ident == null || ident[0] == null)
				this.ident = new String[0];
			else
				this.ident = ident;
			if (advIdent == null || advIdent[0] == null)
				this.advIdent = new String[0];
			else
				this.advIdent = advIdent;
		}

		private WeaponType(int maskID, String ident, String advIdent)
		{
			this(maskID, new String[]
			{ ident }, new String[]
			{ advIdent });
		}

		/**
		 * @param maskID mask identifier
		 * @deprecated to use with weapons that are not needed for any skills
		 */
		@Deprecated
		private WeaponType(int maskID)
		{
			this(maskID, new String[1], new String[1]);
		}

		/**
		 * @return the weapon type mask
		 */
		public final int getMask()
		{
			return mask;
		}

		/**
		 * @return the identification String (explicit specification after
		 * "Requires a ..." or "when using a ..."
		 */
		public final String[] getIdent()
		{
			return ident;
		}

		/**
		 * @return the advanced identification String (part of description)
		 */
		public final String[] getAdvIdent()
		{
			return advIdent;
		}
	}
}
